/**
 * 
 */
package inz.model.classification;

import inz.common.LoggingModule;
import inz.common.MyUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/** Instance based Classifier - Decisions through Emerging Patterns */
public class DeEPs
{
	/** loger */
	private static Logger logger = Logger.getLogger(LoggingModule.CLASSIFICATION_LOGGER);

	/** Classifies example through emerging patterns
	 * @param testExample example to classify
	 * @param examples training examples (DeEPs is lazy classifier)
	 * @return predicted type of example */
	public static ExType classify(Example testExample, Map<ExType, Set<Example>> examples)
	{

		logger.log(Level.INFO, testExample.toString(), "course");

		// build horizontal spaces with examples intersected with given test example for each class
		Map<ExType, Border> borders = new HashMap<ExType, Border>();
		for (Map.Entry<ExType, Set<Example>> entry : examples.entrySet())
		{
			Border border = new Border();
			for (Example ex : entry.getValue())
			{
				border.addExample(Border.intersect(ex.getExampleSet(), testExample.getExampleSet()));
			}
			borders.put(entry.getKey(), border);
			logger.log(Level.INFO, entry.getKey() + "'s border:" + border.toString(), "borders");
		}

		// build JEPs for each class
		Map<ExType, Border> JEPs = new HashMap<ExType, Border>();
		for (Map.Entry<ExType, Border> classEntry : borders.entrySet())
		{
			Border sumOfOtherBorders = new Border();
			for (Map.Entry<ExType, Border> otherClassEntry : borders.entrySet())
			{
				if (classEntry.getKey() != otherClassEntry.getKey())
				{
					sumOfOtherBorders.union(otherClassEntry.getValue());
				}
			}
			Border jep = Border.JepProduce(classEntry.getValue(), sumOfOtherBorders);
			logger.log(Level.INFO, classEntry.getKey() + "'s jep:" + jep.toString(), "jeps");
			JEPs.put(classEntry.getKey(), jep);
		}
		
		// build common Emerging Patterns
		Border commonEPs = null;
		for (Map.Entry<ExType, Border> classEntry : borders.entrySet())
		{
			if (commonEPs == null)
			{
				commonEPs = new Border(classEntry.getValue());
			}
			if (classEntry.getValue() != commonEPs)
			{
				commonEPs.intersect(classEntry.getValue());
			}
		}
		logger.log(Level.INFO, "borders intersection:" + commonEPs.toString(), "course");

		// compute compact scores and choose the best
		ExType predictedType = null;
		double highestScore = 0;
		for (Map.Entry<ExType, Border> classEntry : JEPs.entrySet())
		{
			double thisClassScore = compactScore(classEntry.getValue(), examples.get(classEntry.getKey()), commonEPs);
			logger.log(Level.INFO,
					"Score for:" + classEntry.getKey() + "=" + MyUtils.doubleFormat.format(thisClassScore), "course");
			if (thisClassScore > highestScore)
			{
				highestScore = thisClassScore;
				predictedType = classEntry.getKey();
			}
		}

		logger.log(Level.INFO, "DeEPs classification:" + predictedType, "course");
		return predictedType;
	}

	/** Computes compact score for the class JEP which is percentage of examples that contain left border of JEP
	 * @param jep
	 * @param examples
	 * @param commonEPs
	 * @return compact score */
	private static double compactScore(Border jep, Set<Example> examples, Border commonEPs)
	{
		int occurCounter = 0;

		for (Example example : examples)
		{
			boolean found = false;
			for (Set<Integer> set : jep.getLeft())
			{
				if (example.contains(set))
				{
					occurCounter++;
					found = true;
					break;
				}
			}
			if (found == false)
			{
				for (Set<Integer> set : jep.getRight())
				{
					if (example.contains(set))
					{
						occurCounter++;
						found = true;
						break;
					}
				}

			}
			if (found == false)
			{
				for (Set<Integer> set : commonEPs.getRight())
				{
					if (example.contains(set))
					{
						occurCounter++;
						found = true;
						break;
					}
				}
			}
		}
		return (double) occurCounter / examples.size();
	}
}
